<!DOCTYPE html>
<html lang="zh-CN">
<head hexo-theme='https://github.com/volantis-x/hexo-theme-volantis/tree/4.1.5'>
  <meta charset="utf-8">
  <!-- SEO相关 -->
  
    
  
  <!-- 渲染优化 -->
  <meta http-equiv='x-dns-prefetch-control' content='on' />
  <link rel='dns-prefetch' href='https://cdn.jsdelivr.net'>
  <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
  <meta name="renderer" content="webkit">
  <meta name="force-rendering" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="HandheldFriendly" content="True" >
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <!-- 页面元数据 -->
  
  <title>记一次Next.js搭建之旅 - Alan|前端博客</title>
  
    <meta name="keywords" content="React,Next.js">
  

  
    <meta name="description" content="这段时间工作要求将之前的vue的H5活动页面转移到Next.js下面，经过一段时间的研究，总结出了下面的内容。">
  

  <!-- feed -->
  

  <!-- import meta -->
  

  <!-- link -->
  
    <link rel="shortcut icon" type='image/x-icon' href="https://gitee.com/alannum3/MyImages/raw/master/img/basketball-ball.png">
  

  <!-- import link -->
  

  
    
<link rel="stylesheet" href="/css/first.css">

  

  
  <link rel="stylesheet" href="/css/style.css" media="print" onload="this.media='all';this.onload=null">
  <noscript><link rel="stylesheet" href="/css/style.css"></noscript>
  

  <script id="loadcss"></script>

</head>

<body>
  

<header id="l_header" class="l_header auto shadow blur " style='opacity: 0' >
  <div class='container'>
  <div id='wrapper'>
    <div class='nav-sub'>
      <p class="title"></p>
      <ul class='switcher nav-list-h m-phone' id="pjax-header-nav-list">
        <li><a id="s-comment" class="fas fa-comments fa-fw" target="_self" href='javascript:void(0)'></a></li>
        
          <li><a id="s-toc" class="s-toc fas fa-list fa-fw" target="_self" href='javascript:void(0)'></a></li>
        
      </ul>
    </div>
		<div class="nav-main">
      
        
        <a class="title flat-box" target="_self" href='/'>
          
          
            <i class='fas fa-basketball-ball'></i>
          
          
            Alan
          
        </a>
      

			<div class='menu navigation'>
				<ul class='nav-list-h m-pc'>
          
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/categories/
                  
                  
                  
                    id="categories"
                  >
                  <i class='fas fa-folder-open fa-fw'></i>分类
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/archives/
                  
                  
                  
                    id="archives"
                  >
                  <i class='fas fa-archive fa-fw'></i>归档
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/friends/
                  
                  
                  
                    id="friends"
                  >
                  <i class='fas fa-link fa-fw'></i>友链
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box header toggle-mode-btn">
                  <i class='fas fa-moon fa-fw'></i>dark
                </a>
              <li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" 
                  
                  
                  >
                  <i class='fas fa-ellipsis-v fa-fw'></i>更多
                </a>
                
                  <ul class="list-v">
                    
                      
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=https://alanwang.avosapps.us/
                  
                  
                    target="_blank"
                  
                  
                    id="https:alanwangavosappsus"
                  >
                  <i class='fas fa-comments fa-fw'></i>评论管理
                </a>
                
              </li>
            
          
                    
                  </ul>
                
              </li>
            
          
          
				</ul>
			</div>

      <div class="m_search">
        <form name="searchform" class="form u-search-form">
          <i class="icon fas fa-search fa-fw"></i>
          <input type="text" class="input u-search-input" placeholder="Search..." />
        </form>
      </div>

			<ul class='switcher nav-list-h m-phone'>
				
					<li><a class="s-search fas fa-search fa-fw" target="_self" href='javascript:void(0)'></a></li>
				
				<li>
          <a class="s-menu fas fa-bars fa-fw" target="_self" href='javascript:void(0)'></a>
          <ul class="menu-phone list-v navigation white-box">
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/categories/
                  
                  
                  
                    id="categories"
                  >
                  <i class='fas fa-folder-open fa-fw'></i>分类
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/archives/
                  
                  
                  
                    id="archives"
                  >
                  <i class='fas fa-archive fa-fw'></i>归档
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/friends/
                  
                  
                  
                    id="friends"
                  >
                  <i class='fas fa-link fa-fw'></i>友链
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box header toggle-mode-btn">
                  <i class='fas fa-moon fa-fw'></i>dark
                </a>
              <li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" 
                  
                  
                  >
                  <i class='fas fa-ellipsis-v fa-fw'></i>更多
                </a>
                
                  <ul class="list-v">
                    
                      
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=https://alanwang.avosapps.us/
                  
                  
                    target="_blank"
                  
                  
                    id="https:alanwangavosappsus"
                  >
                  <i class='fas fa-comments fa-fw'></i>评论管理
                </a>
                
              </li>
            
          
                    
                  </ul>
                
              </li>
            
          
            
          </ul>
        </li>
			</ul>
		</div>
	</div>
  </div>
</header>

  <div id="l_body">
    <div id="l_cover">
  
    
        <div id="half" class='cover-wrapper post focus' style="display: ;">
          
            <div id='cover-backstretch'></div>
          
          <div class='cover-body'>
  <div class='top'>
    
    
      <p class="title">Alan</p>
    
    
      <p class="subtitle">此刻想举重若轻，之前必要负重前行</p>
    
  </div>
  <div class='bottom'>
    <div class='menu navigation'>
      <div class='list-h'>
        
          
            <a href="https://alannum3.gitee.io/"
              
              
              id="https:alannum3giteeio">
              <img src='https://cdn.jsdelivr.net/gh/twitter/twemoji@13.0/assets/svg/1f389.svg'><p>Next.js博客</p>
            </a>
          
        
      </div>
    </div>
  </div>
</div>

          <div id="scroll-down" style="display: none;"><i class="fa fa-chevron-down scroll-down-effects"></i></div>
        </div>
    
  
  </div>

    <div id='safearea'>
      <div class='body-wrapper' id="pjax-container">
        

<div class='l_main'>
  <article class="article post white-box reveal md shadow article-type-post" id="post" itemscope itemprop="blogPost">
  


  
  <div class="article-meta" id="top">
    
    
    
      <h1 class="title">
        记一次Next.js搭建之旅
      </h1>
      <div class='new-meta-box'>
        
          
            
  <div class='new-meta-item category'>
    <a class='notlink'>
      <i class="fas fa-folder-open fa-fw" aria-hidden="true"></i>
      <a class="category-link" href="/categories/%E5%89%8D%E7%AB%AF/">前端</a><span class="sep"></span><a class="category-link" href="/categories/%E5%89%8D%E7%AB%AF/React/">React</a>
    </a>
  </div>


          
        
          
            <div class="new-meta-item date" itemprop="dateUpdated" datetime="2020-11-12T17:04:58+08:00">
  <a class='notlink'>
    <i class="fas fa-edit fa-fw" aria-hidden="true"></i>
    <p>更新于：2020年11月12日 17:04</p>
  </a>
</div>

          
        
          
            
  <div class="new-meta-item wordcount">
    <a class='notlink'>
      <i class="fas fa-keyboard fa-fw" aria-hidden="true"></i>
      <p>字数：2.2k字</p>
    </a>
  </div>
  <div class="new-meta-item readtime">
    <a class='notlink'>
      <i class="fas fa-hourglass-half fa-fw" aria-hidden="true"></i>
      <p>时长：11分钟</p>
    </a>
  </div>


          
        
          
            
  <div class="new-meta-item browse leancloud">
    <a class='notlink'>
      
      <div id="lc-pv" data-title="记一次Next.js搭建之旅" data-path="/posts/6af865/">
        <i class="fas fa-fire fa-fw" aria-hidden="true"></i>
        <span id='number'><i class="fas fa-circle-notch fa-spin fa-fw" aria-hidden="true"></i></span>
        次浏览
      </div>
    </a>
  </div>


          
        
      </div>
    
  </div>


  
  <p>这段时间工作要求将之前的vue的H5活动页面转移到Next.js下面，经过一段时间的研究，总结出了下面的内容。</p>
<a id="more"></a>

<h2 id="什么是Next-js"><a href="#什么是Next-js" class="headerlink" title="什么是Next.js"></a>什么是Next.js</h2><p>基于<code>React</code>的SSR（服务端渲染框架）</p>
<h2 id="SSR-amp-CSR"><a href="#SSR-amp-CSR" class="headerlink" title="SSR &amp; CSR"></a>SSR &amp; CSR</h2><blockquote>
<p>参考链接：<a href="https://medium.com/walmartglobaltech/the-benefits-of-server-side-rendering-over-client-side-rendering-5d07ff2cefe8" target="_blank" rel="noopener">https://medium.com/walmartglobaltech/the-benefits-of-server-side-rendering-over-client-side-rendering-5d07ff2cefe8</a></p>
</blockquote>
<img src="" data-original="https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2019/06/ssr-explanation.png?w=800&ssl=1"  />

<blockquote>
<p>The main difference is that for SSR your server’s response to the browser is the HTML of your page that is ready to be rendered, while for CSR the browser gets a pretty empty document with links to your javascript. That means your browser will start rendering the HTML from your server without having to wait for all the JavaScript to be downloaded and executed. In both cases, React will need to be downloaded and go through the same process of building a virtual dom and attaching events to make the page interactive — but for SSR, the user can start viewing the page while all of that is happening. For the CSR world, you need to wait for all of the above to happen and then have the virtual dom moved to the browser dom for the page to be viewable.</p>
</blockquote>
<img src="" data-original="https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2019/06/csr-explanation.png?w=800&ssl=1"  />

<h2 id="Next-js的优点"><a href="#Next-js的优点" class="headerlink" title="Next.js的优点"></a>Next.js的优点</h2><ul>
<li>更好的SEO</li>
<li>更快的首屏渲染速度</li>
</ul>
<h2 id="Next-js-基础-与Rect开发的不同之处"><a href="#Next-js-基础-与Rect开发的不同之处" class="headerlink" title="Next.js 基础(与Rect开发的不同之处)"></a>Next.js 基础(与Rect开发的不同之处)</h2><blockquote>
<p><a href="https://www.nextjs.cn/learn/basics/create-nextjs-app?utm_source=next-site&amp;utm_medium=nav-cta&amp;utm_campaign=next-website" target="_blank" rel="noopener">https://www.nextjs.cn/learn/basics/create-nextjs-app?utm_source=next-site&amp;utm_medium=nav-cta&amp;utm_campaign=next-website</a></p>
</blockquote>
<h3 id="路由映射"><a href="#路由映射" class="headerlink" title="路由映射"></a>路由映射</h3><blockquote>
<p>在 Next.js 中，一个 <strong>page（页面）</strong> 就是一个从 <code>.js</code>、<code>jsx</code>、<code>.ts</code> 或 <code>.tsx</code> 文件导出（export）的 <a href="https://reactjs.org/docs/components-and-props.html" target="_blank" rel="noopener">React 组件</a> ，这些文件存放在 <code>pages</code> 目录下。每个 page（页面）都使用其文件名作为路由（route）。</p>
</blockquote>
<pre><code class="js">pages/about.js/jsx/ts/tsx → /about

pages/dashboard/settings/username.js → /dashboard/settings/username</code></pre>
<h3 id="自带路由：next-router-next-link"><a href="#自带路由：next-router-next-link" class="headerlink" title="自带路由：next/router next/link"></a>自带路由：<code>next/router</code> <code>next/link</code></h3><p>使用与<code>react-router</code>类似，包括编程式跳转<code>router.push</code>以及组件式跳转<code>&lt;Link href=&quot;/about&quot;&gt;&lt;a&gt;click me&lt;/a&gt;&lt;/Link&gt;</code></p>
<pre><code class="js">import { useRouter } from &#39;next/router&#39;;

const router = useRouter();
router.push({
  pathname: &#39;/activities/experience-lesson/course-info&#39;,
  query: { ...queryData, isFree: 0 }
});
router.push(&#39;/about&#39;)</code></pre>
<h3 id="渲染方式"><a href="#渲染方式" class="headerlink" title="渲染方式"></a>渲染方式</h3><h4 id="预渲染"><a href="#预渲染" class="headerlink" title="预渲染"></a>预渲染</h4><ul>
<li><strong>静态生成（Static Generation）</strong>（HTML重用、build生成）</li>
<li><strong>服务器端渲染（Server-side Rendering）</strong>（每次请求生成的HTML不同、用户请求时生成）</li>
</ul>
<h4 id="相关API"><a href="#相关API" class="headerlink" title="相关API"></a>相关API</h4><ul>
<li><p>静态生成</p>
<ul>
<li><code>getStaticProps(context)</code></li>
<li><code>getStaticPaths(context)</code></li>
</ul>
</li>
<li><p>服务器渲染</p>
<ul>
<li><code>getServerSideProps(context)</code></li>
</ul>
</li>
<li><p>客户端获取数据</p>
<ul>
<li>SWR（官方推荐）</li>
</ul>
</li>
</ul>
<p><strong>注意：</strong>在开发环境中<code>getStaticProps</code>和<code>getStaticPaths</code>每次请求都会被调用</p>
<p>使用，在<strong>页面</strong>文件中导出</p>
<pre><code class="js">function Page({ data }) {
  // Render data...
}

// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // Pass data to the page via props
  return { props: { data } }
}

export default Page</code></pre>
<h2 id="项目结构及工程化"><a href="#项目结构及工程化" class="headerlink" title="项目结构及工程化"></a>项目结构及工程化</h2><h3 id="配置Eslint-Prettier"><a href="#配置Eslint-Prettier" class="headerlink" title="配置Eslint+Prettier"></a>配置Eslint+Prettier</h3><blockquote>
<p><a href="https://github.com/paulolramos/eslint-prettier-airbnb-react" target="_blank" rel="noopener">https://github.com/paulolramos/eslint-prettier-airbnb-react</a></p>
<p><a href="https://dev.to/karlhadwen/setup-eslint-prettier-airbnb-style-guide-in-under-2-minutes-a27" target="_blank" rel="noopener">https://dev.to/karlhadwen/setup-eslint-prettier-airbnb-style-guide-in-under-2-minutes-a27</a></p>
<p><a href="https://dev.to/bybruno/configuring-absolute-paths-in-react-for-web-without-ejecting-en-us-52h6" target="_blank" rel="noopener">https://dev.to/bybruno/configuring-absolute-paths-in-react-for-web-without-ejecting-en-us-52h6</a></p>
</blockquote>
<p>解决<code>eslint</code>无法识别动态引入语法<code>import()</code>:</p>
<p><a href="https://github.com/eslint/eslint/issues/11486" target="_blank" rel="noopener">相关issue</a></p>
<pre><code class="js">// eslint 配置
parserOptions: {
  ecmaVersion: 2020, // Use the latest ecmascript standard
  sourceType: &#39;module&#39;, // Allows using import/export statements
  ecmaFeatures: {
  jsx: true // Enable JSX since we&#39;re using React
  }
},</code></pre>
<p>考虑到团队协作，在根目录下创建<code>.vscode</code>文件夹并在里面创建<code>setting.json</code>文件以达到保存自动修复的效果，保证了团队代码的统一性。</p>
<pre><code class="json">{
  &quot;editor.codeActionsOnSave&quot;: {
    &quot;source.fixAll.eslint&quot;: true
  }
}</code></pre>
<p>后期经过一段时间发现有些团队成员的编辑器设置了实时保存功能，于是就直接跳过了这一步，这里便可以用<code>husky</code>(用来给git添加hook)搭配<code>lint-staged</code>在commit代码前来自动做这一件事情。</p>
<pre><code>yarn add husky lint-staged prettier --dev</code></pre><p>编写<code>package.json</code></p>
<pre><code>&quot;husky&quot;: {
 &quot;hooks&quot;: {
    &quot;pre-commit&quot;: &quot;lint-staged&quot;
  }
},
&quot;lint-staged&quot;: {
  &quot;*.{js, jsx}&quot;: [
    &quot;npm run lint&quot;,
    &quot;git add&quot;
  ]
}</code></pre><h3 id="配置alias"><a href="#配置alias" class="headerlink" title="配置alias"></a>配置alias</h3><p><code>next.config.js</code>配置</p>
<pre><code class="js">/* eslint-disable no-param-reassign */
const path = require(&#39;path&#39;);

module.exports = {
  webpack: (config) =&gt; {
    // Note: we provide webpack above so you should not `require` it
    // Perform customizations to webpack config
    config.resolve.alias[&#39;@&#39;] = path.resolve(__dirname, &#39;./src&#39;);
    // Important: return the modified config
    return config;
  }
};
</code></pre>
<p>eslint无法识别alias，需要在根目录下创建文件<code>jsconfig.json</code>并在<code>.eslintrc.js</code>    配置<code>settings</code></p>
<pre><code class="js">// .eslintrc.js
module.exports = {
  root: true, // Make sure eslint picks up the config at the root of the directory
  extends: [&#39;airbnb&#39;, &#39;airbnb/hooks&#39;, &#39;plugin:prettier/recommended&#39;, &#39;prettier/react&#39;],
  env: {
    browser: true,
    commonjs: true,
    es6: true,
    jest: true,
    node: true
  },
  globals: {
    wx: true
  },
  parserOptions: {
    ecmaVersion: 2020, // Use the latest ecmascript standard
    sourceType: &#39;module&#39;, // Allows using import/export statements
    ecmaFeatures: {
      jsx: true // Enable JSX since we&#39;re using React
    }
  },
  rules: {
    &#39;react/react-in-jsx-scope&#39;: 0,
    &#39;jsx-a11y/alt-text&#39;: 0, // img alt
    &#39;react/prop-types&#39;: 0,
    &#39;jsx-a11y/click-events-have-key-events&#39;: 0,
    &#39;jsx-a11y/no-static-element-interactions&#39;: 0,
    &#39;dot-notation&#39;: 0,
    &#39;import/prefer-default-export&#39;: 0,
    &#39;react/jsx-props-no-spreading&#39;: 0,
    &#39;jsx-a11y/href-no-hash&#39;: [&#39;off&#39;],
    &#39;react/no-array-index-key&#39;: 0,
    &#39;no-console&#39;: 0,
    &#39;no-alert&#39;: 0,
    &#39;consistent-return&#39;: 0,
    // eslint-disable-next-line prettier/prettier
    eqeqeq: 1,
    &#39;react/self-closing-comp&#39;: 0,
    &#39;react-hooks/exhaustive-deps&#39;: 0,
    &#39;react/no-danger&#39;: 0,
    &#39;no-shadow&#39;: 0,
    &#39;jsx-a11y/label-has-associated-control&#39;: 0,
    &#39;react/jsx-filename-extension&#39;: [&#39;warn&#39;, { extensions: [&#39;.js&#39;, &#39;.jsx&#39;] }],
    &#39;max-len&#39;: [
      &#39;warn&#39;,
      {
        code: 120,
        tabWidth: 2,
        comments: 120,
        ignoreComments: false,
        ignoreTrailingComments: true,
        ignoreUrls: true,
        ignoreStrings: true,
        ignoreTemplateLiterals: true,
        ignoreRegExpLiterals: true
      }
    ]
  },
  settings: {
    &#39;import/resolver&#39;: {
      alias: {
        map: [[&#39;@&#39;, &#39;./src&#39;]],
        extensions: [&#39;.ts&#39;, &#39;.js&#39;, &#39;.jsx&#39;, &#39;.json&#39;]
      }
    }
  }
};
</code></pre>
<pre><code class="json">{
  &quot;compilerOptions&quot;: {
    &quot;baseUrl&quot;: &quot;src&quot;,
    &quot;paths&quot;: {
      &quot;@/*&quot;: [&quot;./*&quot;]
    }
  },
  &quot;exclude&quot;: [&quot;node_modules&quot;, &quot;**/node_modules/*&quot;]
}</code></pre>
<h3 id="封装axios在每次请求时显示spin组件"><a href="#封装axios在每次请求时显示spin组件" class="headerlink" title="封装axios在每次请求时显示spin组件"></a>封装axios在每次请求时显示<code>spin</code>组件</h3><p>这里要注意一点，由于服务端不存在<code>document</code>，所以要判断一下当前所处的环境再去执行操作。</p>
<pre><code class="jsx">import axios from &#39;axios&#39;;
import ReactDOM from &#39;react-dom&#39;;
import Spin from &#39;../components/Spin/Spin&#39;;

const Axios = axios.create({
  timeout: 20000
});

const csr = process.browser;

// 当前正在请求的数量
let requestCount = 0;

function showLoading() {
  if (requestCount === 0) {
    var dom = document.createElement(&#39;div&#39;);
    dom.setAttribute(&#39;id&#39;, &#39;loading&#39;);
    document.body.appendChild(dom);
    ReactDOM.render(&lt;Spin /&gt;, dom);
  }
  requestCount++;
  console.log(&#39;showLoading&#39;, requestCount);
}

function hideLoading() {
  requestCount--;
  if (requestCount === 0) {
    document.body.removeChild(document.getElementById(&#39;loading&#39;));
  }
  console.log(&#39;hideLoading&#39;, requestCount);
}

Axios.interceptors.request.use(
  (config) =&gt; {
    csr &amp;&amp; showLoading();
    return config;
  },
  (err) =&gt; {
    csr &amp;&amp; hideLoading();
    return Promise.reject(err);
  }
);

Axios.interceptors.response.use(
  (res) =&gt; {
    csr &amp;&amp; hideLoading();
    return res;
  },
  (err) =&gt; {
    csr &amp;&amp; hideLoading();
    return Promise.reject(err);
  }
);

export default Axios;
</code></pre>
<h3 id="自定义input-hook"><a href="#自定义input-hook" class="headerlink" title="自定义input hook"></a>自定义input hook</h3><p>使用后可以免去给每个表单组件设置<code>onChange</code></p>
<pre><code class="jsx">import { useState } from &#39;react&#39;;

// 自定义input hook
// 参考资料：https://rangle.io/blog/simplifying-controlled-inputs-with-hooks/
export function useInput(initialValue) {
  const [value, setValue] = useState(initialValue);

  return {
    value,
    setValue,
    reset: () =&gt; setValue(&#39;&#39;),
    bind: {
      value,
      onChange: (e) =&gt; {
        setValue(e.target.value);
      }
    }
  };
}
</code></pre>
<p>使用：</p>
<pre><code class="jsx">// 没使用前
const [phone, setPhone] = useState(&#39;&#39;);

&lt;input
  name=&quot;phone&quot;
  type=&quot;number&quot;
  placeholder=&quot;请输入您的手机号码(必填)&quot;
  className={`${styles[&#39;cell-content&#39;]} ${styles[&#39;cell-content-right&#39;]}`}
  value={phone}
  onChange={() =&gt; setPhone(e.target.value)}
/&gt;


// 使用后
const { value: phone, bind: bindPhone } = useInput(&#39;&#39;);

&lt;input
  name=&quot;phone&quot;
  type=&quot;number&quot;
  placeholder=&quot;请输入您的手机号码(必填)&quot;
  className={`${styles[&#39;cell-content&#39;]} ${styles[&#39;cell-content-right&#39;]}`}
  {...bindPhone}
/&gt;</code></pre>
<h3 id="封装Dialog"><a href="#封装Dialog" class="headerlink" title="封装Dialog"></a>封装Dialog</h3><pre><code class="jsx">import { createPortal } from &#39;react-dom&#39;;
import styles from &#39;./Modal.module.css&#39;;

export default function Modal({ content, show, onOk }) {
  const modal = show &amp;&amp; (
    &lt;div className={styles[&#39;overlay&#39;]}&gt;
      &lt;div className={styles[&#39;modal&#39;]}&gt;
        {/* 防止冒泡关闭窗口 */}
        &lt;div className={styles[&#39;wrapper&#39;]} onClick={(e) =&gt; e.stopPropagation()}&gt;
          &lt;div className={styles[&#39;content&#39;]}&gt;{content}&lt;/div&gt;
          &lt;div className={styles[&#39;readed_btn&#39;]} onClick={() =&gt; onOk()}&gt;
            好 的
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );

  const ProtalContent = () =&gt; {
    // 用来处理服务端不存在document的问题
    try {
      // 将modal挂在到body上
      return document &amp;&amp; createPortal(modal, document.body);
    } catch (error) {
      return null;
    }
  };

  // 动态引入组件
  // import dynamic from &#39;next/dynamic&#39;;
  // const Modal = dynamic(() =&gt; import(&#39;./components/Modal/Modal&#39;), { ssr: false });

  return (
    &lt;&gt;
      &lt;ProtalContent /&gt;
    &lt;/&gt;
  );
}
</code></pre>
<h3 id="移动端适配"><a href="#移动端适配" class="headerlink" title="移动端适配"></a>移动端适配</h3><p>使用插件<code>postcss-px-to-viewport</code></p>
<p>在根目录下新建文件<code>postcss.config.js</code></p>
<pre><code class="js">module.exports = {
  plugins: {
    &#39;postcss-px-to-viewport&#39;: {
      // 视窗的宽度，对应的是我们设计稿的宽度，我们公司用的是375
      viewportWidth: 375,
      // 视窗的高度，根据750设备的宽度来指定，一般指定1334，也可以不配置
      // viewportHeight: 1334,
      // 指定`px`转换为视窗单位值的小数位数
      unitPrecision: 3,
      // 指定需要转换成的视窗单位，建议使用vw
      viewportUnit: &#39;vw&#39;,
      // 指定不转换为视窗单位的类，可以自定义，可以无限添加,建议定义一至两个通用的类名
      selectorBlackList: [&#39;.ignore&#39;],
      // 小于或等于`1px`不转换为视窗单位，你也可以设置为你想要的值
      minPixelValue: 1,
      // 允许在媒体查询中转换`px`
      mediaQuery: false
      // exclude: undefined
    }
  }
};
</code></pre>
<h3 id="使用Docker-coding实现自动化部署"><a href="#使用Docker-coding实现自动化部署" class="headerlink" title="使用Docker+coding实现自动化部署"></a>使用Docker+coding实现自动化部署</h3><h4 id="dockerfile"><a href="#dockerfile" class="headerlink" title="dockerfile"></a>dockerfile</h4><pre><code class="dockerfile"># node版本号
FROM node:12-alpine

# docker build时传进来的值 docker image build -t &lt;name&gt; --build-arg API_ENV=development .
ARG API_ENV

RUN echo ${API_ENV}

ENV NEXT_PUBLIC_API_ENV=${API_ENV}

# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Install app dependencies
COPY package*.json /usr/src/app/
RUN npm install

# Bundle app source
COPY . /usr/src/app

RUN npm run build
EXPOSE 3000

CMD [ &quot;npm&quot;, &quot;run&quot;, &quot;start&quot; ]</code></pre>
<p>在coding上设置代码push触发规则，触发生成制品库。</p>
<h3 id="使用redux"><a href="#使用redux" class="headerlink" title="使用redux"></a>使用redux</h3><blockquote>
<p><a href="https://github.com/vercel/next.js/tree/canary/examples/with-redux" target="_blank" rel="noopener">https://github.com/vercel/next.js/tree/canary/examples/with-redux</a></p>
<p><a href="https://github.com/vercel/next.js/tree/canary/examples/with-redux-thunk" target="_blank" rel="noopener">https://github.com/vercel/next.js/tree/canary/examples/with-redux-thunk</a></p>
</blockquote>
<p>里面有使用到一个js新特性<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator" target="_blank" rel="noopener">Nullish coalescing operator</a></p>
<h2 id="Next-js-踩坑"><a href="#Next-js-踩坑" class="headerlink" title="Next.js 踩坑"></a>Next.js 踩坑</h2><h3 id="环境变量配置"><a href="#环境变量配置" class="headerlink" title="环境变量配置"></a>环境变量配置</h3><p>环境变量在客户端无法获取，背景：由于我在项目中需要根据环境变量来使用不同环境的API域名。</p>
<p>解决方案：官方提供了以<code>NEXT_PUBLIC_</code>开头的环境变量名，这样就可以在客户端和服务端都访问得到环境变量了。</p>
<h3 id="判断环境"><a href="#判断环境" class="headerlink" title="判断环境"></a>判断环境</h3><pre><code>process.browser === true ? &#39;client&#39; : &#39;server&#39;</code></pre>
  
  
    
    <div class='footer'>
      
      
      
        <div class='copyright'>
          <blockquote>
            
              
                <p>博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议</p>

              
            
              
                <p>本文永久链接是：<a href=https://alanwang.site/posts/6af865/>https://alanwang.site/posts/6af865/</a></p>
              
            
          </blockquote>
        </div>
      
      
    </div>
  
  
    


  <div class='article-meta' id="bottom">
    <div class='new-meta-box'>
      
        
          <div class="new-meta-item date" itemprop="dateUpdated" datetime="2020-11-12T17:04:58+08:00">
  <a class='notlink'>
    <i class="fas fa-edit fa-fw" aria-hidden="true"></i>
    <p>更新于：2020年11月12日 17:04</p>
  </a>
</div>

        
      
        
          
  
  <div class="new-meta-item meta-tags"><a class="tag" href="/tags/React/" rel="nofollow"><i class="fas fa-tags fa-fw" aria-hidden="true"></i><p>React</p></a></div> <div class="new-meta-item meta-tags"><a class="tag" href="/tags/Next-js/" rel="nofollow"><i class="fas fa-tags fa-fw" aria-hidden="true"></i><p>Next.js</p></a></div>


        
      
        
          
  <div class="new-meta-item share -mob-share-list">
  <div class="-mob-share-list share-body">
    
      
        <a class="-mob-share-qq" title="" rel="external nofollow noopener noreferrer"
          
          href="http://connect.qq.com/widget/shareqq/index.html?url=https://alanwang.site/posts/6af865/&title=记一次Next.js搭建之旅 - Alan|前端博客&summary=这段时间工作要求将之前的vue的H5活动页面转移到Next.js下面，经过一段时间的研究，总结出了下面的内容。"
          
          >
          
            <img src="" data-original="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/logo/128/qq.png">
          
        </a>
      
    
      
        <a class="-mob-share-qzone" title="" rel="external nofollow noopener noreferrer"
          
          href="https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=https://alanwang.site/posts/6af865/&title=记一次Next.js搭建之旅 - Alan|前端博客&summary=这段时间工作要求将之前的vue的H5活动页面转移到Next.js下面，经过一段时间的研究，总结出了下面的内容。"
          
          >
          
            <img src="" data-original="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/logo/128/qzone.png">
          
        </a>
      
    
      
        <a class="-mob-share-weibo" title="" rel="external nofollow noopener noreferrer"
          
          href="http://service.weibo.com/share/share.php?url=https://alanwang.site/posts/6af865/&title=记一次Next.js搭建之旅 - Alan|前端博客&summary=这段时间工作要求将之前的vue的H5活动页面转移到Next.js下面，经过一段时间的研究，总结出了下面的内容。"
          
          >
          
            <img src="" data-original="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/logo/128/weibo.png">
          
        </a>
      
    
      
    
      
    
  </div>
</div>



        
      
    </div>
  </div>


  
  

  
    <div class="prev-next">
      
        <a class='prev' href='/posts/92c49d45/'>
          <p class='title'><i class="fas fa-chevron-left" aria-hidden="true"></i>博客重构之路</p>
          <p class='content'>事实上，这并不算是一次重构，更像是对我工作这段时间所学的内容的一次总结，也是对自己技术提升的一种要求，它是一个技术驱动下产生的产品。
项目启动时间：2020-11-22


博客搭建之路其实之前...</p>
        </a>
      
      
        <a class='next' href='/posts/c05e5480/'>
          <p class='title'>React入门学习总结<i class="fas fa-chevron-right" aria-hidden="true"></i></p>
          <p class='content'>总结学习React以及相关插件时遇到的难点以及重点



基础知识点
单向数据流（子组件不能修改父组件数据）

组件名首字母大写

可以使用&lt;React.Fragment&gt;&lt;/...</p>
        </a>
      
    </div>
  
</article>


  

  <article class="post white-box reveal shadow" id="comments">
    <p ct><i class='fas fa-comments'></i> 评论</p>
    
    <div id="valine_container" class="valine_thread">
  <i class="fas fa-cog fa-spin fa-fw fa-2x"></i>
</div>

  </article>






</div>
<aside class='l_side'>
  
  
    
    



  <section class="widget toc-wrapper shadow desktop mobile" id="toc-div" >
    
  <header>
    
      <i class="fas fa-list fa-fw" aria-hidden="true"></i><span class='name'>本文目录</span>
    
  </header>


    <div class='content'>
        <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#什么是Next-js"><span class="toc-text">什么是Next.js</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#SSR-amp-CSR"><span class="toc-text">SSR &amp; CSR</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Next-js的优点"><span class="toc-text">Next.js的优点</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Next-js-基础-与Rect开发的不同之处"><span class="toc-text">Next.js 基础(与Rect开发的不同之处)</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#路由映射"><span class="toc-text">路由映射</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#自带路由：next-router-next-link"><span class="toc-text">自带路由：next&#x2F;router next&#x2F;link</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#渲染方式"><span class="toc-text">渲染方式</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#预渲染"><span class="toc-text">预渲染</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#相关API"><span class="toc-text">相关API</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#项目结构及工程化"><span class="toc-text">项目结构及工程化</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#配置Eslint-Prettier"><span class="toc-text">配置Eslint+Prettier</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#配置alias"><span class="toc-text">配置alias</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#封装axios在每次请求时显示spin组件"><span class="toc-text">封装axios在每次请求时显示spin组件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#自定义input-hook"><span class="toc-text">自定义input hook</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#封装Dialog"><span class="toc-text">封装Dialog</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#移动端适配"><span class="toc-text">移动端适配</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#使用Docker-coding实现自动化部署"><span class="toc-text">使用Docker+coding实现自动化部署</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#dockerfile"><span class="toc-text">dockerfile</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#使用redux"><span class="toc-text">使用redux</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Next-js-踩坑"><span class="toc-text">Next.js 踩坑</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#环境变量配置"><span class="toc-text">环境变量配置</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#判断环境"><span class="toc-text">判断环境</span></a></li></ol></li></ol>
    </div>
  </section>


  


</aside>



        
        
          <!--此文件用来存放一些不方便取值的变量-->
<!--思路大概是将值藏到重加载的区域内-->

<script>
  window.pdata={}
  pdata.ispage=true;
  pdata.postTitle="记一次Next.js搭建之旅";
  pdata.commentPath="";
  pdata.commentPlaceholder="";

  var l_header=document.getElementById("l_header");
  
  l_header.classList.remove("show");
  
</script>

        
      </div>
      
  
  <footer class="footer clearfix">
    <br><br>
    
      
        <div class="aplayer-container">
          


        </div>
      
    
      
        <br>
        <div class="social-wrapper">
          
            
              <a href="mailto:alannum3@qq.com"
                class="social fas fa-envelope flat-btn"
                target="_blank"
                rel="external nofollow noopener noreferrer">
                
              </a>
            
          
            
              <a href="https://github.com/3Alan"
                class="social fab fa-github flat-btn"
                target="_blank"
                rel="external nofollow noopener noreferrer">
                
              </a>
            
          
            
              <a href="tencent://message/?uin=1540703192"
                class="social fab fa-qq flat-btn"
                target="_blank"
                rel="external nofollow noopener noreferrer">
                
              </a>
            
          
            
          
            
          
        </div>
      
    
      
        <div><p>博客内容遵循 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh" target="_blank" rel="noopener">署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议</a></p>
</div>
      
    
      
        <div class='copyright'>
        <p><a href="https://alanwang.site/">Copyright © 2020 Alan</a></p>

        </div>
      
    
  </footer>


      <a id="s-top" class="fas fa-arrow-up fa-fw" href='javascript:void(0)'></a>
    </div>
  </div>
  <div>
    <script>
window.volantis={};
window.volantis.loadcss=document.getElementById("loadcss");
/********************脚本懒加载函数********************************/
function loadScript(src, cb) {
var HEAD = document.getElementsByTagName('head')[0] || document.documentElement;
var script = document.createElement('script');
script.setAttribute('type','text/javascript');
if (cb) script.onload = cb;
script.setAttribute('src', src);
HEAD.appendChild(script);
}
//https://github.com/filamentgroup/loadCSS
var loadCSS = function( href, before, media, attributes ){
	var doc = window.document;
	var ss = doc.createElement( "link" );
	var ref;
	if( before ){
		ref = before;
	}
	else {
		var refs = ( doc.body || doc.getElementsByTagName( "head" )[ 0 ] ).childNodes;
		ref = refs[ refs.length - 1];
	}
	var sheets = doc.styleSheets;
	if( attributes ){
		for( var attributeName in attributes ){
			if( attributes.hasOwnProperty( attributeName ) ){
				ss.setAttribute( attributeName, attributes[attributeName] );
			}
		}
	}
	ss.rel = "stylesheet";
	ss.href = href;
	ss.media = "only x";
	function ready( cb ){
		if( doc.body ){
			return cb();
		}
		setTimeout(function(){
			ready( cb );
		});
	}
	ready( function(){
		ref.parentNode.insertBefore( ss, ( before ? ref : ref.nextSibling ) );
	});
	var onloadcssdefined = function( cb ){
		var resolvedHref = ss.href;
		var i = sheets.length;
		while( i-- ){
			if( sheets[ i ].href === resolvedHref ){
				return cb();
			}
		}
		setTimeout(function() {
			onloadcssdefined( cb );
		});
	};
	function loadCB(){
		if( ss.addEventListener ){
			ss.removeEventListener( "load", loadCB );
		}
		ss.media = media || "all";
	}
	if( ss.addEventListener ){
		ss.addEventListener( "load", loadCB);
	}
	ss.onloadcssdefined = onloadcssdefined;
	onloadcssdefined( loadCB );
	return ss;
};
</script>
<script>
  
  loadCSS("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.14/css/all.min.css", window.volantis.loadcss);
  
  
  
  
  loadCSS("https://cdn.jsdelivr.net/npm/highlight.js@10.0.3/styles/atom-one-dark.min.css", window.volantis.loadcss);
  
</script>
<!-- required -->

<script src="https://cdn.jsdelivr.net/npm/jquery@3.5/dist/jquery.min.js"></script>

<script>
  function pjax_fancybox() {
    $(".md .gallery").find("img").each(function () { //渲染 fancybox
      var element = document.createElement("a"); // a 标签
      $(element).attr("class", "fancybox");
      $(element).attr("pjax-fancybox", "");  // 过滤 pjax
      $(element).attr("href", $(this).attr("src"));
      if ($(this).attr("data-original")) {
        $(element).attr("href", $(this).attr("data-original"));
      }
      $(element).attr("data-fancybox", "images");
      var caption = "";   // 描述信息
      if ($(this).attr('alt')) {  // 判断当前页面是否存在描述信息
        $(element).attr('data-caption', $(this).attr('alt'));
        caption = $(this).attr('alt');
      }
      var div = document.createElement("div");
      $(div).addClass("fancybox");
      $(this).wrap(div); // 最外层套 div ，其实主要作用还是 class 样式
      var span = document.createElement("span");
      $(span).addClass("image-caption");
      $(span).text(caption); // 加描述
      $(this).after(span);  // 再套一层描述
      $(this).wrap(element);  // 最后套 a 标签
    })
    $(".md .gallery").find("img").fancybox({
      selector: '[data-fancybox="images"]',
      hash: false,
      loop: false,
      closeClick: true,
      helpers: {
        overlay: {closeClick: true}
      },
      buttons: [
        "zoom",
        "close"
      ]
    });
  };
  function SCload_fancybox() {
    if ($(".md .gallery").find("img").length == 0) return;
    loadCSS("https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css", document.getElementById("loadcss"));
    setTimeout(function() {
      loadScript('https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js', pjax_fancybox)
    }, 1);
  };
  $(function () {
    SCload_fancybox();
  });
</script>


<!-- internal -->

  
  
  
    
<script src="https://cdn.jsdelivr.net/npm/jquery-backstretch@2.1.18/jquery.backstretch.min.js"></script>

    <script type="text/javascript">
        var imgs=["https://gitee.com/alannum3/MyImages/raw/master/img/triangles-3321900_1920.png", "https://gitee.com/alannum3/MyImages/raw/master/img/img-8e2c27a1a7510d3b73317e96ec2f4b72.jpg", "https://gitee.com/alannum3/MyImages/raw/master/img/img-2da8b0f6ac052b7b9402dea246a56701.jpg", "https://gitee.com/alannum3/MyImages/raw/master/img/img-389c21fac02e9980506b6f7ff7f130f6.jpg", "https://gitee.com/alannum3/MyImages/raw/master/img/img-b7f5935e94f6deda4fda5e2820c48bef.jpg", "https://gitee.com/alannum3/MyImages/raw/master/img/img-3c991ba94430b0ddf426d9062bbea403.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/002.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/003.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/004.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/005.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/006.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/012.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/016.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/019.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/025.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/033.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/034.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/035.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/038.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/039.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/042.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/046.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/051.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/052.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/054.jpg", "https://cdn.jsdelivr.net/gh/volantis-x/cdn-wallpaper-minimalist/2020/056.jpg"];
        if ('true' == 'true') {
          function shuffle(arr){
            /*From countercurrent-time*/
            var n = arr.length;
            while(n--) {
              var index = Math.floor(Math.random() * n);
              var temp = arr[index];
              arr[index] = arr[n];
              arr[n] = temp;
            }
          }
          shuffle(imgs);
        }
	  function Pjax_backstretch(){
        
          $.backstretch(
            imgs,
          {
            duration: "20000",
            fade: "1500"
          });
        
	  }
	  loadScript("https://cdn.jsdelivr.net/npm/jquery-backstretch@2.1.18/jquery.backstretch.min.js",Pjax_backstretch)
    </script>
  



  
  

  <div id="rightmenu-wrapper">
    <ul class="list-v rightmenu" id="rightmenu-content">
      <li class='option'>
        <a class='vlts-menu opt fix-cursor-default' id='menu-copy-text' onclick="document.execCommand('copy')"><i class='fa fa-copy fa-fw'></i>复制文本</a>
        <hr id='hr-text'>
        <a class='vlts-menu opt fix-cursor-default' id='menu-copy-href'><i class='fa fa-link fa-fw'></i>复制链接</a>
        <a class='vlts-menu opt fix-cursor-default' id='menu-open-href'><i class='fa fa-external-link-square-alt fa-fw'></i>在新标签页打开</a>
        <hr id='hr-href'>
        <a class='vlts-menu opt fix-cursor-default' id='menu-copy-src'><i class='fa fa-image fa-fw'></i>复制图片地址</a>
        <hr id='hr-src'>
      </li>
      
        <li class='navigation'>
          <a class='nav icon-only fix-cursor-default' onclick='history.back()'><i class='fa fa-arrow-left fa-fw'></i></a>
          <a class='nav icon-only fix-cursor-default' onclick='history.forward()'><i class='fa fa-arrow-right fa-fw'></i></a>
          <a class='nav icon-only fix-cursor-default' onclick='window.location.reload()'><i class='fa fa-redo fa-fw'></i></a>
          <a class='nav icon-only fix-cursor-default' href='/'><i class='fa fa-home fa-fw'></i></a>
        </li>
      
      <hr>
      
        
      
        
      
    </ul>
  </div>

  <script>
    window.document.oncontextmenu = function (event) {
      if (event.ctrlKey) return true;
      if (/Android|webOS|BlackBerry/i.test(navigator.userAgent)) return true;
      return popMenu(event);
    };
    document.addEventListener("click", function (event) {
      var mymenu = document.getElementById('rightmenu-wrapper');
      mymenu.style.display = "none";
    });
    function popMenu(event) {
      var mymenu = document.getElementById('rightmenu-wrapper');
      var menuContent = document.getElementById('rightmenu-content');
      var screenWidth = document.documentElement.clientWidth || document.body.clientWidth;
      var screenHeight = document.documentElement.clientHeight || document.body.clientHeight;
      mymenu.style.left = event.clientX + "px"; // 获取鼠标位置
      mymenu.style.top = event.clientY + "px";
      mymenu.style.display = 'block';
      if (event.clientX * 2 > screenWidth) {
        menuContent.classList.add('left');
      } else {
        menuContent.classList.remove('left');
      }
      if (event.clientY * 2 > screenHeight) {
        menuContent.classList.add('top');
      } else {
        menuContent.classList.remove('top');
      }

      let hrText = document.getElementById('hr-text');
      let hrSrc = document.getElementById('hr-src');
      let hrHref = document.getElementById('hr-href');

      // 选中图片
      let copySrc = document.getElementById('menu-copy-src');
      if (copySrc != undefined) {
        if (event.target.currentSrc) {
          copySrc.style.display = 'block';
          copySrc.addEventListener("click", function (e) {
            copyString(event.target.currentSrc);
          },{once: true});
          hrSrc.style.display = 'block';
        } else {
          copySrc.style.display = 'none';
          hrSrc.style.display = 'none';
        }
      }

      // 选中按钮
      // 判断是不是按钮
      let href = '';
      if (event.path) {
        for (i = 0; i < event.path.length; i++) {
          if (event.path[i].href != undefined && event.path[i].href.length > 0) {
            href = event.path[i].href;
          }
        }
      }

      let copyText = document.getElementById('menu-copy-text');
      copyText.style.display = 'none';
      hrText.style.display = 'none';
      if (href.length == 0) {
        // 选中文本
        if (window.getSelection().toString()) {
          if ('true' == 'true') {
            copyText.style.display = 'block';
            hrText.style.display = 'block';
          }
        }
      }

      // 在新标签页打开
      let openHref = document.getElementById('menu-open-href');
      if (openHref != undefined) {
        if (href.length > 0) {
          openHref.style.display = 'block';
          openHref.addEventListener("click", function (e) {
            window.open(href);
          },{once: true});
          hrHref.style.display = 'block';
        } else {
          openHref.style.display = 'none';
          hrHref.style.display = 'none';
        }
      }
      // 复制链接
      let copyHref = document.getElementById('menu-copy-href');
      if (copyHref != undefined) {
        if (href.length > 0) {
          copyHref.style.display = 'block';
          copyHref.addEventListener("click", function (e) {
            copyString(href);
          },{once: true});
        } else {
          copyHref.style.display = 'none';
        }
      }

      // 有音乐播放器
      if ('false' == true && ('home,music'||[]).includes('music')) {
        // 如果有aplayer，初始化一下
      	try {
      		checkAPlayer();
          updateTitle();
      	} catch (error) {
      		console.log(error);
      	}
      }

      return false; // 该行代码确保系统自带的右键菜单不会被调出
    }
    function hideMenu() {
      document.getElementById('rightmenu-wrapper').style.display = 'none';
    }
    function copyString(str) {
      const input = document.createElement('input');
      input.setAttribute('readonly', 'readonly');
      document.body.appendChild(input);
      input.setAttribute('value', str);
      input.select();
      document.execCommand('copy');
      document.body.removeChild(input);
    }
    document.execCommand('click');
  </script>







  <script defer src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@17.1.0/dist/lazyload.min.js"></script>
<script>
  // https://www.npmjs.com/package/vanilla-lazyload
  // Set the options globally
  // to make LazyLoad self-initialize
  window.lazyLoadOptions = {
    elements_selector: ".lazyload",
    threshold: 0
  };
  // Listen to the initialization event
  // and get the instance of LazyLoad
  window.addEventListener(
    "LazyLoad::Initialized",
    function (event) {
      window.lazyLoadInstance = event.detail.instance;
    },
    false
  );
  document.addEventListener('DOMContentLoaded', function () {
    lazyLoadInstance.update();
  });
  document.addEventListener('pjax:complete', function () {
    lazyLoadInstance.update();
  });
</script>




  
  
    <script>
      window.FPConfig = {
        delay: 0,
        ignoreKeywords: [],
        maxRPS: 5,
        hoverDelay: 25
      };
    </script>
    <script defer src="https://cdn.jsdelivr.net/gh/gijo-varghese/flying-pages@2.1.2/flying-pages.min.js"></script>
  




  <script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
    var clipboard = new ClipboardJS('.btn-copy', {
        target: function (trigger) {
            return trigger.nextElementSibling
        }
    });
    function wait(callback, seconds) {
        var timelag = null;
        timelag = window.setTimeout(callback, seconds)
    }
    function pjax_initCopyCode() {
		if($(".highlight .code pre").length+$(".article pre code").length==0)return;
        var copyHtml = '';
        copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
        copyHtml += '<i class="fas fa-copy"></i><span>COPY</span>';
        copyHtml += '</button>';
        $(".highlight .code pre").before(copyHtml);
        $(".article pre code").before(copyHtml);
        clipboard.off('success').on('success', function (e) {
            let $btn = $(e.trigger);
            $btn.addClass('copied');
            let $icon = $($btn.find('i'));
            $icon.removeClass('fa-copy');
            $icon.addClass('fa-check-circle');
            let $span = $($btn.find('span'));
            $span[0].innerText = 'COPIED';
            wait(function () {
                $icon.removeClass('fa-check-circle');
                $icon.addClass('fa-copy');
                $span[0].innerText = 'COPY'
            }, 2000)
        });
        clipboard.off('error').on('error', function (e) {
            e.clearSelection();
            let $btn = $(e.trigger);
            $btn.addClass('copy-failed');
            let $icon = $($btn.find('i'));
            $icon.removeClass('fa-copy');
            $icon.addClass('fa-times-circle');
            let $span = $($btn.find('span'));
            $span[0].innerText = 'COPY FAILED';
            wait(function () {
                $icon.removeClass('fa-times-circle');
                $icon.addClass('fa-copy');
                $span[0].innerText = 'COPY'
            }, 2000)
        })
    }
    $(function () {
        pjax_initCopyCode()
    });
</script>








  
  
<script src="https://cdn.jsdelivr.net/npm/valine@1.4/dist/Valine.min.js"></script>


<script>
  var GUEST_INFO = ['nick', 'mail', 'link'];
  var meta = 'nick,mail,link'.split(',').filter(function (item) {
    return GUEST_INFO.indexOf(item) > -1
  });
  var REQUIRED_FIELDS = ['nick', 'mail', 'link'];
  var requiredFields = 'nick,mail'.split(',').filter(function (item) {
    return REQUIRED_FIELDS.indexOf(item) > -1
  });

  function emoji(path, idx, ext) {
    return path + "/" + path + "-" + idx + "." + ext;
  }

  var emojiMaps = {};
  for (var i = 1; i <= 54; i++) {
    emojiMaps['tieba-' + i] = emoji('tieba', i, 'png');
  }
  for (var i = 1; i <= 101; i++) {
    emojiMaps['qq-' + i] = emoji('qq', i, 'gif');
  }
  for (var i = 1; i <= 116; i++) {
    emojiMaps['aru-' + i] = emoji('aru', i, 'gif');
  }
  for (var i = 1; i <= 125; i++) {
    emojiMaps['twemoji-' + i] = emoji('twemoji', i, 'png');
  }
  for (var i = 1; i <= 4; i++) {
    emojiMaps['weibo-' + i] = emoji('weibo', i, 'png');
  }

  function pjax_valine() {
    if(!document.querySelectorAll("#valine_container")[0])return;

    let pagePlaceholder = pdata.commentPlaceholder || "快来评论吧~";

    let path = pdata.commentPath;
    if (path.length == 0) {
      let defaultPath = '';
      path = defaultPath || decodeURI(window.location.pathname);
    }

    var valine = new Valine();
    valine.init({
      el: '#valine_container',
      meta: meta,
      placeholder: pagePlaceholder,
      path: path,
      appId: "aowgxqHE7wB4I8HLWqC4c4Ut-MdYXbMMI",
      appKey: "37wkY4T3cM7nV2UWV7EIEHPy",
      pageSize: '10',
      avatar: 'robohash',
      lang: 'zh-cn',
      visitor: 'true',
      highlight: 'true',
      mathJax: 'false',
      enableQQ: 'true',
      recordIP: 'false',
      requiredFields: requiredFields,
      emojiCDN: 'https://cdn.jsdelivr.net/gh/volantis-x/cdn-emoji/valine/',
      emojiMaps: emojiMaps
    })
  }

  $(function () {
    pjax_valine();
  });
</script>




  <script defer src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-busuanzi@2.3/js/busuanzi.pure.mini.js" data-pjax></script>


  
<script src="https://cdn.jsdelivr.net/npm/hexo-theme-volantis@4.1.5/source/js/app.min.js"></script>




  
  
<script src="https://cdn.jsdelivr.net/npm/hexo-theme-volantis@4.1.5/source/js/search.min.js"></script>

  


<!-- optional -->

  <script>
const SearchServiceimagePath="https://cdn.jsdelivr.net/gh/volantis-x/cdn-volantis@master/img/";
const ROOT =  ("/" || "/").endsWith('/') ? ("/" || "/") : ("//" || "/" );
function listenSearch(){
  
    customSearch = new HexoSearch({
      imagePath: SearchServiceimagePath
    });
  
}
document.addEventListener("DOMContentLoaded", listenSearch);

</script>







  
<script src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-volantis@2/js/comment_typing.js"></script>




  
    
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/highlight.min.js"></script>

    <script>hljs.initHighlightingOnLoad();</script>
  



  <script defer>

  const LCCounter = {
    app_id: 'aowgxqHE7wB4I8HLWqC4c4Ut-MdYXbMMI',
    app_key: '37wkY4T3cM7nV2UWV7EIEHPy',
    custom_api_server: '',

    // 查询存储的记录
    getRecord(Counter, url, title) {
      return new Promise(function (resolve, reject) {
        Counter('get', '/classes/Counter?where=' + encodeURIComponent(JSON.stringify({url})))
          .then(resp => resp.json())
          .then(({results, code, error}) => {
            if (code === 401) {
              throw error;
            }
            if (results && results.length > 0) {
              var record = results[0];
              resolve(record);
            } else {
              Counter('post', '/classes/Counter', {url, title: title, times: 0})
                .then(resp => resp.json())
                .then((record, error) => {
                  if (error) {
                    throw error;
                  }
                  resolve(record);
                }).catch(error => {
                console.error('Failed to create', error);
                reject(error);
              });
            }
          }).catch((error) => {
          console.error('LeanCloud Counter Error:', error);
          reject(error);
        });
      })
    },

    // 发起自增请求
    increment(Counter, incrArr) {
      return new Promise(function (resolve, reject) {
        Counter('post', '/batch', {
          "requests": incrArr
        }).then((res) => {
          res = res.json();
          if (res.error) {
            throw res.error;
          }
          resolve(res);
        }).catch((error) => {
          console.error('Failed to save visitor count', error);
          reject(error);
        });
      });
    },

    // 构建自增请求体
    buildIncrement(objectId) {
      return {
        "method": "PUT",
        "path": `/1.1/classes/Counter/${ objectId }`,
        "body": {
          "times": {
            '__op': 'Increment',
            'amount': 1
          }
        }
      }
    },

    // 校验是否为有效的 UV
    validUV() {
      var key = 'LeanCloudUVTimestamp';
      var flag = localStorage.getItem(key);
      if (flag) {
        // 距离标记小于 24 小时则不计为 UV
        if (new Date().getTime() - parseInt(flag) <= 86400000) {
          return false;
        }
      }
      localStorage.setItem(key, new Date().getTime().toString());
      return true;
    },

    addCount(Counter) {
      var enableIncr = '' === 'true' && window.location.hostname !== 'localhost';
      enableIncr = true;
      var getterArr = [];
      var incrArr = [];
      // 请求 PV 并自增
      var pvCtn = document.querySelector('#lc-sv');
      if (pvCtn || enableIncr) {
        var pvGetter = this.getRecord(Counter, 'https://alanwang.site' + '/#lc-sv', 'Visits').then((record) => {
          incrArr.push(this.buildIncrement(record.objectId))
          var eles = document.querySelectorAll('#lc-sv #number');
          if (eles.length > 0) {
            eles.forEach((el,index,array)=>{
              el.innerText = record.times + 1;
              if (pvCtn) {
                pvCtn.style.display = 'inline';
              }
            })
          }
        });
        getterArr.push(pvGetter);
      }

      // 请求 UV 并自增
      var uvCtn = document.querySelector('#lc-uv');
      if (uvCtn || enableIncr) {
        var uvGetter = this.getRecord(Counter, 'https://alanwang.site' + '/#lc-uv', 'Visitors').then((record) => {
          var vuv = this.validUV();
          vuv && incrArr.push(this.buildIncrement(record.objectId))
          var eles = document.querySelectorAll('#lc-uv #number');
          if (eles.length > 0) {
            eles.forEach((el,index,array)=>{
              el.innerText = record.times + (vuv ? 1 : 0);
              if (uvCtn) {
                uvCtn.style.display = 'inline';
              }
            })
          }
        });
        getterArr.push(uvGetter);
      }

      // 请求文章的浏览数，如果是当前页面就自增
      var allPV = document.querySelectorAll('#lc-pv');
      if (allPV.length > 0 || enableIncr) {
        for (i = 0; i < allPV.length; i++) {
          let pv = allPV[i];
          let title = pv.getAttribute('data-title');
          var url = 'https://alanwang.site' + pv.getAttribute('data-path');
          if (url) {
            var viewGetter = this.getRecord(Counter, url, title).then((record) => {
              // 是当前页面就自增
              let curPath = window.location.pathname;
              if (curPath.includes('index.html')) {
                curPath = curPath.substring(0, curPath.lastIndexOf('index.html'));
              }
              if (pv.getAttribute('data-path') == curPath) {
                incrArr.push(this.buildIncrement(record.objectId));
              }
              if (pv) {
                var ele = pv.querySelector('#lc-pv #number');
                if (ele) {
                  if (pv.getAttribute('data-path') == curPath) {
                    ele.innerText = (record.times || 0) + 1;
                  } else {
                    ele.innerText = record.times || 0;
                  }
                  pv.style.display = 'inline';
                }
              }
            });
            getterArr.push(viewGetter);
          }
        }
      }

      // 如果启动计数自增，批量发起自增请求
      if (enableIncr) {
        Promise.all(getterArr).then(() => {
          incrArr.length > 0 && this.increment(Counter, incrArr);
        })
      }

    },


    fetchData(api_server) {
      var Counter = (method, url, data) => {
        return fetch(`${ api_server }/1.1${ url }`, {
          method,
          headers: {
            'X-LC-Id': this.app_id,
            'X-LC-Key': this.app_key,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(data)
        });
      };
      this.addCount(Counter);
    },

    refreshCounter() {
      var api_server = this.app_id.slice(-9) !== '-MdYXbMMI' ? this.custom_api_server : `https://${ this.app_id.slice(0, 8).toLowerCase() }.api.lncldglobal.com`;
      if (api_server) {
        this.fetchData(api_server);
      } else {
        fetch('https://app-router.leancloud.cn/2/route?appId=' + this.app_id)
          .then(resp => resp.json())
          .then(({api_server}) => {
            this.fetchData('https://' + api_server);
          });
      }
    }

  };

  LCCounter.refreshCounter();

  document.addEventListener('pjax:complete', function () {
    LCCounter.refreshCounter();
  });
</script>




  <script>
const rootElement = document.documentElement;
const darkModeStorageKey = "user-color-scheme";
const rootElementDarkModeAttributeName = "data-user-color-scheme";

const setLS = (k, v) => {
  try {
    localStorage.setItem(k, v);
  } catch (e) {
    console.log(e.message);
  }
};

const removeLS = (k) => {
  try {
    localStorage.removeItem(k);
  } catch (e) {
    console.log(e.message);
  }
};

const getLS = (k) => {
  try {
    return localStorage.getItem(k);
  } catch (e) {
    console.log(e.message);
    return null;
  }
};

const getModeFromCSSMediaQuery = () => {
  return window.matchMedia("(prefers-color-scheme: dark)").matches
    ? "dark"
    : "light";
};

const resetRootDarkModeAttributeAndLS = () => {
  rootElement.removeAttribute(rootElementDarkModeAttributeName);
  removeLS(darkModeStorageKey);
};

const validColorModeKeys = {
  dark: true,
  light: true,
};

const applyCustomDarkModeSettings = (mode) => {
  const currentSetting = mode || getLS(darkModeStorageKey);

  if (currentSetting === getModeFromCSSMediaQuery()) {
    resetRootDarkModeAttributeAndLS();
  } else if (validColorModeKeys[currentSetting]) {
    rootElement.setAttribute(rootElementDarkModeAttributeName, currentSetting);
  } else {
    resetRootDarkModeAttributeAndLS();
  }
};

const invertDarkModeObj = {
  dark: "light",
  light: "dark",
};

/**
 * get target mode
 */
const toggleCustomDarkMode = () => {
  let currentSetting = getLS(darkModeStorageKey);

  if (validColorModeKeys[currentSetting]) {
    currentSetting = invertDarkModeObj[currentSetting];
  } else if (currentSetting === null) {
    currentSetting = invertDarkModeObj[getModeFromCSSMediaQuery()];
  } else {
    return;
  }
  setLS(darkModeStorageKey, currentSetting);
  return currentSetting;
};

/**
 * bind click event for toggle button
 */
function bindToggleButton() {
	var btn=$("#wrapper .toggle-mode-btn");
    btn.on('click',(e) => {
      const mode = toggleCustomDarkMode();
      applyCustomDarkModeSettings(mode);
    });
}

applyCustomDarkModeSettings();
document.addEventListener("DOMContentLoaded", bindToggleButton);
document.addEventListener("pjax:success", bindToggleButton);

</script>





<!-- baidu Analytics -->
<script>
var _hmt = _hmt || [];
(function() {
  setTimeout(function() {
	var hm = document.createElement("script");
	hm.src = "https://hm.baidu.com/hm.js?ff1856f91533fcea9c8c2fe5a1b06fe7";
	hm.defer=true;
	var s = document.getElementsByTagName("script")[0];
	s.parentNode.insertBefore(hm, s);
  }, 5000);
})();
</script>


<script>
function listennSidebarTOC() {
  const navItems = document.querySelectorAll(".toc li");
  if (!navItems.length) return;
  const sections = [...navItems].map((element) => {
    const link = element.querySelector(".toc-link");
    const target = document.getElementById(
      decodeURI(link.getAttribute("href")).replace("#", "")
    );
    link.addEventListener("click", (event) => {
      event.preventDefault();
      window.scrollTo({
		top: target.offsetTop + 100,
		
		behavior: "smooth"
		
	  });
    });
    return target;
  });

  function activateNavByIndex(target) {
    if (target.classList.contains("active-current")) return;

    document.querySelectorAll(".toc .active").forEach((element) => {
      element.classList.remove("active", "active-current");
    });
    target.classList.add("active", "active-current");
    let parent = target.parentNode;
    while (!parent.matches(".toc")) {
      if (parent.matches("li")) parent.classList.add("active");
      parent = parent.parentNode;
    }
  }

  function findIndex(entries) {
    let index = 0;
    let entry = entries[index];
    if (entry.boundingClientRect.top > 0) {
      index = sections.indexOf(entry.target);
      return index === 0 ? 0 : index - 1;
    }
    for (; index < entries.length; index++) {
      if (entries[index].boundingClientRect.top <= 0) {
        entry = entries[index];
      } else {
        return sections.indexOf(entry.target);
      }
    }
    return sections.indexOf(entry.target);
  }

  function createIntersectionObserver(marginTop) {
    marginTop = Math.floor(marginTop + 10000);
    let intersectionObserver = new IntersectionObserver(
      (entries, observe) => {
        let scrollHeight = document.documentElement.scrollHeight + 100;
        if (scrollHeight > marginTop) {
          observe.disconnect();
          createIntersectionObserver(scrollHeight);
          return;
        }
        let index = findIndex(entries);
        activateNavByIndex(navItems[index]);
      },
      {
        rootMargin: marginTop + "px 0px -100% 0px",
        threshold: 0,
      }
    );
    sections.forEach((element) => {
      element && intersectionObserver.observe(element);
    });
  }
  createIntersectionObserver(document.documentElement.scrollHeight);
}

document.addEventListener("DOMContentLoaded", listennSidebarTOC);
document.addEventListener("pjax:success", listennSidebarTOC);
</script>

<!-- more -->




    
      


<script src="https://cdn.jsdelivr.net/npm/pjax@0.2.8/pjax.min.js"></script>


<script>
    var pjax;
    document.addEventListener('DOMContentLoaded', function () {
      pjax = new Pjax({
        elements: 'a[href]:not([href^="#"]):not([href="javascript:void(0)"]):not([pjax-fancybox])',
        selectors: [
          "title",
          "#l_cover",
          "#pjax-container",
          "#pjax-header-nav-list"
        ],
        cacheBust: false,   // url 地址追加时间戳，用以避免浏览器缓存
        timeout: 5000
      });
    });

    document.addEventListener('pjax:send', function (e) {
      //window.stop(); // 相当于点击了浏览器的停止按钮

      try {
        var currentUrl = window.location.pathname;
        var targetUrl = e.triggerElement.href;
        var banUrl = [""];
        if (banUrl[0] != "") {
          banUrl.forEach(item => {
            if(currentUrl.indexOf(item) != -1 || targetUrl.indexOf(item) != -1) {
              window.location.href = targetUrl;
            }
          });
        }
      } catch (error) {}

      window.subData = null; // 移除标题（用于一二级导航栏切换处）
      if (typeof $.fancybox != "undefined") {
        $.fancybox.close();    // 关闭弹窗
      }
      volantis.$switcher.removeClass('active'); // 关闭移动端激活的搜索框
      volantis.$header.removeClass('z_search-open'); // 关闭移动端激活的搜索框
      volantis.$wrapper.removeClass('sub'); // 跳转页面时关闭二级导航

      // 解绑事件 避免重复监听
      volantis.$topBtn.unbind('click');
      $('.menu a').unbind('click');
      $(window).unbind('resize');
      $(window).unbind('scroll');
      $(document).unbind('scroll');
      $(document).unbind('click');
      $('body').unbind('click');
	  
    });

    document.addEventListener('pjax:complete', function () {
      // 关于百度统计对 SPA 页面的处理：
      // 方案一：百度统计>管理>单页应用设置中，打开开启按钮即可对SPA进行统计。 https://tongji.baidu.com/web/help/article?id=324
      // 方案二：取消注释下列代码。 https://tongji.baidu.com/web/help/article?id=235
      // 
      // _hmt.push(['_trackPageview', document.location.pathname]);
      // 

      // 关于谷歌统计对 SPA 页面的处理：
      // 当应用以动态方式加载内容并更新地址栏中的网址时，也应该更新通过 gtag.js 存储的网页网址。
      // https://developers.google.cn/analytics/devguides/collection/gtagjs/single-page-applications?hl=zh-cn
      

      $('.nav-main').find('.list-v').not('.menu-phone').removeAttr("style",""); // 移除小尾巴的移除
      $('.menu-phone.list-v').removeAttr("style",""); // 移除小尾巴的移除
      $('script[data-pjax], .pjax-reload script').each(function () {
        $(this).parent().append($(this).remove());
      });
      try{
          if (typeof $.fancybox == "undefined") {
            SCload_fancybox();
          } else {
            pjax_fancybox();
          }
        
		  Pjax_backstretch()
        
        
        document.querySelectorAll('pre code').forEach((block) => {
          hljs.highlightBlock(block);
        });
        
        
        
        
          pjax_initCopyCode();
        
        
          pjax_valine();
        
        
        
        
      } catch (e) {
        console.log(e);
      }
	  
    });

    document.addEventListener('pjax:error', function (e) {
	  
      window.location.href = e.triggerElement.href;
    });
</script>

    
  </div>
<script>
            window.imageLazyLoadSetting = {
                isSPA: false,
                processImages: null,
            };
        </script><script>window.addEventListener("load",function(){var t=/\.(gif|jpg|jpeg|tiff|png)$/i,r=/^data:image\/[a-z]+;base64,/;Array.prototype.slice.call(document.querySelectorAll("img[data-original]")).forEach(function(a){var e=a.parentNode;"A"===e.tagName&&(e.href.match(t)||e.href.match(r))&&(e.href=a.dataset.original)})});</script><script>!function(n){n.imageLazyLoadSetting.processImages=i;var e=n.imageLazyLoadSetting.isSPA,r=Array.prototype.slice.call(document.querySelectorAll("img[data-original]"));function i(){e&&(r=Array.prototype.slice.call(document.querySelectorAll("img[data-original]")));for(var t,a=0;a<r.length;a++)0<=(t=(t=r[a]).getBoundingClientRect()).bottom&&0<=t.left&&t.top<=(n.innerHeight||document.documentElement.clientHeight)&&function(){var t,e,n,i,o=r[a];t=o,e=function(){r=r.filter(function(t){return o!==t})},n=new Image,i=t.getAttribute("data-original"),n.onload=function(){t.src=i,e&&e()},n.src=i}()}i(),n.addEventListener("scroll",function(){var t,e;t=i,e=n,clearTimeout(t.tId),t.tId=setTimeout(function(){t.call(e)},500)})}(this);</script></body>
</html>
